/*********************************************************************
*
*      Copyright (C) 2002 Andrew Khan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
***************************************************************************/

package jxl.read.biff;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;

import common.Logger;

import jxl.WorkbookSettings;
import jxl.biff.BaseCompoundFile;
import jxl.biff.IntegerHelper;
import jxl.biff.Type;

/**
 * File containing the data from the binary stream
 */
public class File
{
  /**
   * The logger
   */
  private static Logger logger = Logger.getLogger(File.class);

  /**
   * The data from the excel 97 file
   */
  private byte[] data;
  /**
   * The current position within the file
   */
  private int filePos;
  /**
   * The saved pos
   */
  private int oldPos;
  /**
   * The initial file size
   */
  private int initialFileSize;
  /**
   * The amount to increase the growable array by
   */
  private int arrayGrowSize;
  /**
   * A handle to the compound file. This is only preserved when the
   * copying of PropertySets is enabled
   */
  private CompoundFile compoundFile;
  /**
   * The workbook settings
   */
  private WorkbookSettings workbookSettings;

  /**
   * Constructs a file from the input stream
   *
   * @param is the input stream
   * @param ws the workbook settings
   * @exception IOException
   * @exception BiffException
   */
  public File(InputStream is, WorkbookSettings ws)
    throws IOException, BiffException
  {
    // Initialize the file sizing parameters from the settings
    workbookSettings = ws;
    initialFileSize = workbookSettings.getInitialFileSize();
    arrayGrowSize = workbookSettings.getArrayGrowSize();

    byte[] d = new byte[initialFileSize];
    int bytesRead = is.read(d);
    int pos = bytesRead;

    // Handle thread interruptions, in case the user keeps pressing
    // the Submit button from a browser.  Thanks to Mike Smith for this
    if (Thread.currentThread().isInterrupted())
    {
      throw new InterruptedIOException();
    }

    while (bytesRead != -1)
    {
      if (pos >= d.length)
      {
        // Grow the array
        byte[] newArray = new byte[d.length + arrayGrowSize];
        System.arraycopy(d, 0, newArray, 0, d.length);
        d = newArray;
      }
      bytesRead = is.read(d, pos, d.length - pos);
      pos += bytesRead;

      if (Thread.currentThread().isInterrupted())
      {
        throw new InterruptedIOException();
      }
    }

    bytesRead = pos + 1;

    // Perform file reading checks and throw exceptions as necessary
    if (bytesRead == 0)
    {
      throw new BiffException(BiffException.excelFileNotFound);
    }

    CompoundFile cf = new CompoundFile(d, ws);
    try
    {
      data = cf.getStream("workbook");
    }
    catch (BiffException e)
    {
      // this might be in excel 95 format - try again
      data = cf.getStream("book");
    }

    if (!workbookSettings.getPropertySetsDisabled() &&
        (cf.getNumberOfPropertySets() >
         BaseCompoundFile.STANDARD_PROPERTY_SETS.length))
    {
      compoundFile = cf;
    }

    cf = null;

    if (!workbookSettings.getGCDisabled())
    {
      System.gc();
    }

    // Uncomment the following lines to send the pure workbook stream
    // (ie. a defragged ole stream) to an output file

//      FileOutputStream fos = new FileOutputStream("defraggedxls");
//      fos.write(data);
//      fos.close();

  }

  /**
   * Constructs a file from already defragged binary data.  Useful for
   * displaying subportions of excel streams.  This is only used during
   * special runs of the "BiffDump" demo program and should not be invoked
   * as part of standard JExcelApi parsing
   *
   * @param d the already parsed data
   */
  public File(byte[] d)
  {
    data = d;
  }

  /**
   * Returns the next data record and increments the pointer
   *
   * @return the next data record
   */
  Record next()
  {
    Record r = new Record(data, filePos, this);
    return r;
  }

  /**
   * Peek ahead to the next record, without incrementing the file position
   *
   * @return the next record
   */
  Record peek()
  {
    int tempPos = filePos;
    Record r = new Record(data, filePos, this);
    filePos = tempPos;
    return r;
  }

  /**
   * Skips forward the specified number of bytes
   *
   * @param bytes the number of bytes to skip forward
   */
  public void skip(int bytes)
  {
    filePos += bytes;
  }

  /**
   * Copies the bytes into a new array and returns it.
   *
   * @param pos the position to read from
   * @param length the number of bytes to read
   * @return The bytes read
   */
  public byte[] read(int pos, int length)
  {
    byte[] ret = new byte[length];
    try
    {
      System.arraycopy(data, pos, ret, 0, length);
    }
    catch (ArrayIndexOutOfBoundsException e)
    {
      logger.error("Array index out of bounds at position " + pos +
                   " record length " + length);
      throw e;
    }
    return ret;
  }

  /**
   * Gets the position in the stream
   *
   * @return the position in the stream
   */
  public int getPos()
  {
    return filePos;
  }

  /**
   * Saves the current position and temporarily sets the position to be the
   * new one.  The original position may be restored usind the restorePos()
   * method. This is used when reading in the cell values of the sheet - an
   * addition in 1.6 for memory allocation reasons.
   *
   * These methods are used by the SheetImpl.readSheet() when it is reading
   * in all the cell values
   *
   * @param p the temporary position
   */
  public void setPos(int p)
  {
    oldPos = filePos;
    filePos = p;
  }

  /**
   * Restores the original position
   *
   * These methods are used by the SheetImpl.readSheet() when it is reading
   * in all the cell values
   */
  public void restorePos()
  {
    filePos = oldPos;
  }

  /**
   * Moves to the first bof in the file
   */
  private void moveToFirstBof()
  {
    boolean bofFound = false;
    while (!bofFound)
    {
      int code = IntegerHelper.getInt(data[filePos], data[filePos + 1]);
      if (code == Type.BOF.value)
      {
        bofFound = true;
      }
      else
      {
        skip(128);
      }
    }
  }

  /**
   * "Closes" the biff file
   *
   * @deprecated As of version 1.6 use workbook.close() instead
   */
  public void close()
  {
  }

  /**
   * Clears the contents of the file
   */
  public void clear()
  {
    data = null;
  }

  /**
   * Determines if the current position exceeds the end of the file
   *
   * @return TRUE if there is more data left in the array, FALSE otherwise
   */
  public boolean hasNext()
  {
    // Allow four bytes for the record code and its length
    return filePos < data.length - 4;
  }

  /**
   * Accessor for the compound file.  The returned value will only be non-null
   * if the property sets feature is enabled and the workbook contains
   * additional property sets
   *
   * @return the compound file
   */
  CompoundFile getCompoundFile()
  {
    return compoundFile;
  }
}
